﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QuickGraph;
using System.Xml.Linq;
using System.IO;
using System.Xml;

namespace cilada.engine.Graph
{
    public class QuickGraphAdapter<TVertex, TEdge> : IGraphAdapter<TVertex, TEdge>
        where TEdge : TaggedEdge<TVertex, EdgeType>
        where TVertex : IMember
    {
        #region Constants
        static readonly XNamespace NS_GraphML = "http://graphml.graphdrawing.org/xmlns";
        static readonly XNamespace XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
        #endregion

        #region IGraphAdapter<TVertex,TEdge> Properties
        public IEnumerable<TVertex> Vertices
        {
            get
            {
                return graph.Vertices;
            }
        }

        public IEnumerable<TEdge> Edges
        {
            get
            {
                return graph.Edges;
            }
        }
        #endregion

        #region Fields
        private AdjacencyGraph<TVertex, TEdge> graph;
        #endregion

        #region IGraphAdapter<TVertex,TEdge> Members
        public QuickGraphAdapter()
        {
            // create a new graph that does not allow parallel edges.
            graph = new AdjacencyGraph<TVertex, TEdge>(false);
        }

        public void AddEdge(TEdge edge)
        {
            graph.AddVerticesAndEdge(edge);
        }

        public void AddVertex(TVertex vertex)
        {
            graph.AddVertex(vertex);
        }

        public bool ContainsVertex(TVertex vertex)
        {
            return this.graph.ContainsVertex(vertex);
        }

        public bool ContainsVertex(Func<TVertex, bool> pred, out TVertex vertex)
        {
            TVertex v = GetVertex(pred);
            if (v != null)
            {
                vertex = v;
                return true;
            }
            else
            {
                vertex = null;
                return false;
            }
        }

        public TVertex GetVertex(Func<TVertex, bool> pred)
        {
            return this.graph.Vertices.FirstOrDefault(pred);
        }

        public void Graph2XML(string outputFileName, string outputDirectory)
        {

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = new System.Text.UTF8Encoding(false);
            settings.Indent = true;
            settings.CloseOutput = true;
            string outputFile = Path.Combine(outputDirectory, outputFileName);

            // create output dir if it doesnt exist.
            if (!Directory.Exists(outputDirectory))
            {
                Directory.CreateDirectory(outputDirectory);
            }

            using (XmlWriter xmlWriter = XmlWriter.Create(outputFile, settings))
            {
                xmlWriter.WriteStartDocument();

                xmlWriter.WriteStartElement("graphml", NS_GraphML.NamespaceName);
                xmlWriter.WriteAttributeString("xmlns", "xsi", null, XSI_NS.NamespaceName);
                xmlWriter.WriteAttributeString("xsi", "schemaLocation", XSI_NS.NamespaceName, @"http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd");

                xmlWriter.WriteStartElement("graph");
                xmlWriter.WriteAttributeString("edgedefault", "directed");

                //xmlWriter.WriteAttributeString("xsi", "noNamespaceSchemaLocation", null, "AGraphML.xsd");

                IDictionary<string, int> vertexIds = new Dictionary<string, int>();
                int v_id = 0;
                foreach (TVertex v in this.Vertices)
                {
                    vertexIds.Add(v.FullName, v_id);

                    XElement vertex = new XElement(NS_GraphML+"node");
                    vertex.Add(new XAttribute("id", "v" + v_id));
                    vertex.Add(GetVertexAttributes(v));

                    vertex.WriteTo(xmlWriter);

                    v_id++;
                }

                int e_id = 0;
                foreach (TEdge e in this.Edges)
                {
                    XElement edge = new XElement(NS_GraphML+"edge", new XAttribute("id", "e" + e_id));

                    int source_id = vertexIds[e.Source.FullName];
                    int target_id = vertexIds[e.Target.FullName];
                    edge.Add(new XAttribute("source", "v" + source_id));
                    edge.Add(new XAttribute("target", "v" + target_id));
                    edge.Add(new XAttribute("type", e.Tag));

                    edge.WriteTo(xmlWriter);

                    e_id++;
                }

                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndElement();

                xmlWriter.WriteEndDocument();

                xmlWriter.Close();
            }
        }

        public void PrintGraphToConsole()
        {
            PrintGraphVertices();
            PrintGraphEdges();
        }

        public void PrintGraphVertices()
        {
            Console.WriteLine("Graph Vertices:");
            foreach (IMember m in this.Vertices)
            {
                Console.WriteLine(m.ToString());
            }
        }

        public void PrintGraphEdges()
        {
            Console.WriteLine("Graph Edges:");
            foreach (TaggedEdge<TVertex, EdgeType> edge in this.Edges)
            {
                Console.WriteLine("{0} -{1}-> {2}", edge.Source, edge.Tag, edge.Target);
            }
        }

        private List<XAttribute> GetVertexAttributes(TVertex v)
        {
            List<XAttribute> attributes = new List<XAttribute>();
            attributes.Add(new XAttribute("name", v.FullName));
            attributes.Add(new XAttribute("access", v.AccessLevel));

            if (v.IsAttribute())
            {
                attributes.Add(new XAttribute("vtype", "attribute"));
            }
            else if (v.IsMethod())
            {
                attributes.Add(new XAttribute("vtype", "method"));
            }
            else if (v.IsType())
            {
                attributes.Add(new XAttribute("vtype", "type"));
                TypeVertex tv = v as TypeVertex;
                attributes.Add(new XAttribute("namepace", tv.Namespace));
                attributes.Add(new XAttribute("container", tv.Container ?? ""));
                attributes.Add(new XAttribute("isNestedType", tv.IsNestedType));
                attributes.Add(new XAttribute("isAbstract", tv.IsAbstract));
                attributes.Add(new XAttribute("isInterface", tv.IsInterface));
                attributes.Add(new XAttribute("type", tv.Type));
            }
            else
            {
                throw new ArgumentException("v is of an unknown type (" + v.GetType().ToString() + ")");
            }

            return attributes;
        }
        #endregion
    }
}
